﻿using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class UGUIEventTriggerListener : EventTrigger
{
    //Tips:
    //IPointerEnterHandler - OnPointerEnter - Called when a pointer enters the object
    //IPointerExitHandler - OnPointerExit - Called when a pointer exits the object
    //IPointerDownHandler - OnPointerDown - Called when a pointer is pressed on the object
    //IPointerUpHandler - OnPointerUp - Called when a pointer is released (called on the original the pressed object)
    //IPointerClickHandler - OnPointerClick - Called when a pointer is pressed and released on the same object
    //IInitializePotentialDragHandler - OnInitializePotentialDrag - Called when a drag target is found, can be used to initialise values
    //IBeginDragHandler - OnBeginDrag - Called on the drag object when dragging is about to begin
    //IDragHandler - OnDrag - Called on the drag object when a drag is happening
    //IEndDragHandler - OnEndDrag - Called on the drag object when a drag finishes
    //IDropHandler - OnDrop - Called on the object where a drag finishes
    //IScrollHandler - OnScroll - Called when a mouse wheel scrolls
    //IUpdateSelectedHandler - OnUpdateSelected - Called on the selected object each tick
    //ISelectHandler - OnSelect - Called when the object becomes the selected object
    //IDeselectHandler - OnDeselect - Called on the selected object becomes deselected
    //IMoveHandler - OnMove - Called when a move event occurs (left, right, up, down, ect)
    //ISubmitHandler - OnSubmit - Called when the submit button is pressed
    //ICancelHandler - OnCancel - Called when the cancel button is pressed

    public delegate void VoidDelegate(GameObject go);

    public VoidDelegate onPointerClick;
    public VoidDelegate onPointerDown;
    public VoidDelegate onPointerEnter;
    public VoidDelegate onPointerExit;
    public VoidDelegate onPointerUp;

    public VoidDelegate onInitializePotentialDrag;
    public VoidDelegate onBeginDrag;
    public VoidDelegate onDrag;
    public VoidDelegate onEndDrag;
    public VoidDelegate onDrop;

    public VoidDelegate onMove;
    public VoidDelegate onScroll;

    public VoidDelegate onSelect;
    public VoidDelegate onDeselect;
    public VoidDelegate onUpdateSelected;

    public VoidDelegate onSubmit;
    public VoidDelegate onCancel;

    [SerializeField]
    public UGUIEventType forwardEvents;

    [SerializeField]
    public UGUIEventType reRaycastEvents;

    [SerializeField]
    public bool cancelClickEventOnMove = true;

    private bool _pointerMovedDuringClick;

    private GraphicRaycaster _graphicRayCaster;

    public static UGUIEventTriggerListener Get(GameObject go)
    {
        return go.GetComponent<UGUIEventTriggerListener>() ?? go.AddComponent<UGUIEventTriggerListener>();
    }

    #region Pointer Events
    public override void OnPointerClick(PointerEventData eventData)
    {
        if (cancelClickEventOnMove && _pointerMovedDuringClick)
        {
            return;
        }

        base.OnPointerClick(eventData);
        eventData.Reset();
        if (onPointerClick != null) { onPointerClick(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnPointerClick) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IPointerClickHandler>();
            if (parentHandler != null) { parentHandler.OnPointerClick(eventData); }
        }
        eventData.Reset();
        if ((reRaycastEvents & UGUIEventType.OnPointerClick) != 0)
        {
            var parentHandler = GetReRaycastHandler<IPointerClickHandler>(eventData);
            if (parentHandler != null) { parentHandler.OnPointerClick(eventData); }
        }
    }

    public override void OnPointerDown(PointerEventData eventData)
    {
        _pointerMovedDuringClick = false;

        base.OnPointerDown(eventData);
        eventData.Reset();
        if (onPointerDown != null) { onPointerDown(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnPointerDown) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IPointerDownHandler>();
            if (parentHandler != null) { parentHandler.OnPointerDown(eventData); }
        }
        eventData.Reset();
        if ((reRaycastEvents & UGUIEventType.OnPointerDown) != 0)
        {
            var parentHandler = GetReRaycastHandler<IPointerDownHandler>(eventData);
            if (parentHandler != null) { parentHandler.OnPointerDown(eventData); }
        }
    }

    public override void OnPointerEnter(PointerEventData eventData)
    {
        base.OnPointerEnter(eventData);
        eventData.Reset();
        if (onPointerEnter != null) { onPointerEnter(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnPointerEnter) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IPointerEnterHandler>();
            if (parentHandler != null) { parentHandler.OnPointerEnter(eventData); }
        }
        eventData.Reset();
        if ((reRaycastEvents & UGUIEventType.OnPointerEnter) != 0)
        {
            var parentHandler = GetReRaycastHandler<IPointerEnterHandler>(eventData);
            if (parentHandler != null) { parentHandler.OnPointerEnter(eventData); }
        }
    }

    public override void OnPointerExit(PointerEventData eventData)
    {
        base.OnPointerExit(eventData);
        eventData.Reset();
        if (onPointerExit != null) { onPointerExit(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnPointerExit) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IPointerExitHandler>();
            if (parentHandler != null) { parentHandler.OnPointerExit(eventData); }
        }
        eventData.Reset();
        if ((reRaycastEvents & UGUIEventType.OnPointerExit) != 0)
        {
            var parentHandler = GetReRaycastHandler<IPointerExitHandler>(eventData);
            if (parentHandler != null) { parentHandler.OnPointerExit(eventData); }
        }
    }

    public override void OnPointerUp(PointerEventData eventData)
    {
        base.OnPointerUp(eventData);
        eventData.Reset();
        if (onPointerUp != null) { onPointerUp(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnPointerUp) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IPointerUpHandler>();
            if (parentHandler != null) { parentHandler.OnPointerUp(eventData); }
        }
        eventData.Reset();
        if ((reRaycastEvents & UGUIEventType.OnPointerUp) != 0)
        {
            var parentHandler = GetReRaycastHandler<IPointerUpHandler>(eventData);
            if (parentHandler != null) { parentHandler.OnPointerUp(eventData); }
        }
    }
    #endregion

    #region DragEvents
    public override void OnInitializePotentialDrag(PointerEventData eventData)
    {
        base.OnInitializePotentialDrag(eventData);
        eventData.Reset();
        if (onInitializePotentialDrag != null) { onInitializePotentialDrag(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnInitializePotentialDrag) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IInitializePotentialDragHandler>();
            if (parentHandler != null) { parentHandler.OnInitializePotentialDrag(eventData); }
        }
        eventData.Reset();
        if ((reRaycastEvents & UGUIEventType.OnInitializePotentialDrag) != 0)
        {
            var parentHandler = GetReRaycastHandler<IInitializePotentialDragHandler>(eventData);
            if (parentHandler != null) { parentHandler.OnInitializePotentialDrag(eventData); }
        }
    }

    public override void OnBeginDrag(PointerEventData eventData)
    {
        base.OnBeginDrag(eventData);
        eventData.Reset();
        if (onBeginDrag != null) { onBeginDrag(gameObject); }
        _pointerMovedDuringClick = true;

        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnBeginDrag) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IBeginDragHandler>();
            if (parentHandler != null) { parentHandler.OnBeginDrag(eventData); }
        }
        eventData.Reset();
        if ((reRaycastEvents & UGUIEventType.OnBeginDrag) != 0)
        {
            var parentHandler = GetReRaycastHandler<IBeginDragHandler>(eventData);
            if (parentHandler != null) { parentHandler.OnBeginDrag(eventData); }
        }
    }

    public override void OnDrag(PointerEventData eventData)
    {
        base.OnDrag(eventData);
        eventData.Reset();
        if (onDrag != null) { onDrag(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnDrag) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IDragHandler>();
            if (parentHandler != null) { parentHandler.OnDrag(eventData); }
        }
        eventData.Reset();
        if ((reRaycastEvents & UGUIEventType.OnDrag) != 0)
        {
            var parentHandler = GetReRaycastHandler<IDragHandler>(eventData);
            if (parentHandler != null) { parentHandler.OnDrag(eventData); }
        }
    }

    public override void OnEndDrag(PointerEventData eventData)
    {
        base.OnEndDrag(eventData);
        eventData.Reset();
        if (onEndDrag != null) { onEndDrag(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnEndDrag) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IEndDragHandler>();
            if (parentHandler != null) { parentHandler.OnEndDrag(eventData); }
        }
        eventData.Reset();
        if ((reRaycastEvents & UGUIEventType.OnEndDrag) != 0)
        {
            var parentHandler = GetReRaycastHandler<IEndDragHandler>(eventData);
            if (parentHandler != null) { parentHandler.OnEndDrag(eventData); }
        }
    }

    public override void OnDrop(PointerEventData eventData)
    {
        base.OnDrop(eventData);
        eventData.Reset();
        if (onDrop != null) { onDrop(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnDrop) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IDropHandler>();
            if (parentHandler != null) { parentHandler.OnDrop(eventData); }
        }
        eventData.Reset();
        if ((reRaycastEvents & UGUIEventType.OnDrop) != 0)
        {
            var parentHandler = GetReRaycastHandler<IDropHandler>(eventData);
            if (parentHandler != null) { parentHandler.OnDrop(eventData); }
        }
    }
    #endregion

    #region Other Pointer Related Events
    public override void OnScroll(PointerEventData eventData)
    {
        base.OnScroll(eventData);
        eventData.Reset();
        if (onScroll != null) { onScroll(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnScroll) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IScrollHandler>();
            if (parentHandler != null) { parentHandler.OnScroll(eventData); }
        }
        eventData.Reset();
        if ((reRaycastEvents & UGUIEventType.OnScroll) != 0)
        {
            var parentHandler = GetReRaycastHandler<IScrollHandler>(eventData);
            if (parentHandler != null) { parentHandler.OnScroll(eventData); }
        }
    }
    #endregion

    #region Navigation Events
    public override void OnSelect(BaseEventData eventData)
    {
        base.OnSelect(eventData);
        eventData.Reset();
        if (onSelect != null) { onSelect(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnSelect) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<ISelectHandler>();
            if (parentHandler != null) { parentHandler.OnSelect(eventData); }
        }
    }

    public override void OnDeselect(BaseEventData eventData)
    {
        base.OnDeselect(eventData);
        eventData.Reset();
        if (onDeselect != null) { onDeselect(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnDeselect) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IDeselectHandler>();
            if (parentHandler != null) { parentHandler.OnDeselect(eventData); }
        }
    }

    public override void OnUpdateSelected(BaseEventData eventData)
    {
        base.OnUpdateSelected(eventData);
        eventData.Reset();
        if (onUpdateSelected != null) { onUpdateSelected(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnUpdateSelected) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IUpdateSelectedHandler>();
            if (parentHandler != null) { parentHandler.OnUpdateSelected(eventData); }
        }
    }
    #endregion

    #region Other Events
    public override void OnMove(AxisEventData eventData)
    {
        base.OnMove(eventData);
        eventData.Reset();
        if (onMove != null) { onMove(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnMove) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<IMoveHandler>();
            if (parentHandler != null) { parentHandler.OnMove(eventData); }
        }
    }

    public override void OnSubmit(BaseEventData eventData)
    {
        base.OnSubmit(eventData);
        eventData.Reset();
        if (onSubmit != null) { onSubmit(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnSubmit) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<ISubmitHandler>();
            if (parentHandler != null) { parentHandler.OnSubmit(eventData); }
        }
    }

    public override void OnCancel(BaseEventData eventData)
    {
        base.OnCancel(eventData);
        eventData.Reset();
        if (onCancel != null) { onCancel(gameObject); }
        eventData.Reset();
        if ((forwardEvents & UGUIEventType.OnCancel) != 0)
        {
            var parentHandler = GetComponentInParentExcludeSelf<ICancelHandler>();
            if (parentHandler != null) { parentHandler.OnCancel(eventData); }
        }
    }
    #endregion

    #region Event Functions
    public void ForwardAllEvents()
    {
        forwardEvents = UGUIEventTypeConst.AllEvents;
    }

    public void BlockAllEvents()
    {
        forwardEvents = UGUIEventTypeConst.NoneEvent;
    }

    public void ForwardPointerEvents()
    {
        forwardEvents |= UGUIEventTypeConst.PointerEvents;
    }

    public void BlockPointerEvents()
    {
        forwardEvents &= ~UGUIEventTypeConst.PointerEvents;
    }

    public void ForwardDragEvents()
    {
        forwardEvents |= UGUIEventTypeConst.DragEvents;
    }

    public void BlockDragEvents()
    {
        forwardEvents &= ~UGUIEventTypeConst.DragEvents;
    }

    public void ForwardSelectEvents()
    {
        forwardEvents |= UGUIEventTypeConst.SelectEvents;
    }

    public void BlockSelectEvents()
    {
        forwardEvents &= ~UGUIEventTypeConst.SelectEvents;
    }
    #endregion

    private List<RaycastResult> __raycastTargetList;
    private T GetReRaycastHandler<T>(PointerEventData eventData)
        where T : IEventSystemHandler
    {
        T selectedHandler = default(T);

        var raycaster = GetRaycaster();

        if (raycaster != null)
        {
            if (__raycastTargetList == null)
            {
                __raycastTargetList = new List<RaycastResult>();
            }

            __raycastTargetList.Clear();
            raycaster.Raycast(eventData, __raycastTargetList);

            if (__raycastTargetList.Count > 0)
            {
                var go = this.gameObject;

                for (int i = 0; i < __raycastTargetList.Count; i++)
                {
                    var target = __raycastTargetList[i];

                    if (selectedHandler == null && target.gameObject != go)
                    {
                        selectedHandler = target.gameObject.GetComponent<T>();
                    }

                    target.Clear();
                }

                __raycastTargetList.Clear();
            }
        }

        return selectedHandler;
    }

    private GraphicRaycaster GetRaycaster()
    {
        if (_graphicRayCaster == null)
        {
            _graphicRayCaster = GetComponentInParent<GraphicRaycaster>();
        }

        return _graphicRayCaster;
    }

    private T GetComponentInParentExcludeSelf<T>()
    {
        return this.transform.parent != null ? this.transform.parent.GetComponentInParent<T>() : default(T);
    }
}